#include "Entity.h"
#include "Player.h"

#include "..\..\Line.h"
#include "..\\Message Management\\DeleteObjectMessage.h"
#include "..\Tile Management\\TileManager.h"
#include "..\\Game.h"


Entity::Entity()
{
	bOnWall = false;
	fWallTime = 0.0f;
	bOnGround = false;
	fInvulnerableTimer = 0.0f;
	fAttackTimer = 0.0f;
	fAttackResetTime = .25f;
	bIsAttacking = false;
}


Entity::~Entity()
{
}

int Entity::Update(float dt)
{
	// Update invulnerable timer if currently invulnerable
	if (IsInvulnerable())
		SetInvulnerableTimer(GetInvulnerableTimer() - dt);

	if (!IsOnGround())
	{
		// Apply gravity to Acceleration
		SetAcceleration(SGD::Vector(GetAcceleration().x, GetAcceleration().y + dt * 5000.0f));

		if (GetAcceleration().y > 2500.0f)
			SetAcceleration(SGD::Vector(GetAcceleration().x, 2500.0f));
	}

	// Check for Tile Collision
	TileCollision(dt);

	// Call MobileObjects Update
	MobileObject::Update(dt);

	return 0;
}

void Entity::HandleCollision(const GameObject* collision)
{
}

void Entity::HandleAttackCollision(const GameObject* collision)
{
	const Entity* opponent = dynamic_cast<const Entity*>(collision);
	int i = (opponent->GetDamage() + int(abs(opponent->GetVelocity().x) * .05f));

	// Apply Damage
	if (opponent->GetID() == PLAYER)
		SetCurrentHP(GetCurrentHP() - (opponent->GetDamage() + int(abs(opponent->GetVelocity().x) * .05f)));
	else
		SetCurrentHP(GetCurrentHP() - opponent->GetDamage());

	// Check if Dead
	if (GetCurrentHP() <= 0 && !GetIsKilled())
	{
		// Set Boss death animation
		if (GetID() == BOSS && GAME->GetCurrentLevel() == 3)
		{
			if (GAME->GetCurrentLevel() == 1)
			{
				// Add Qudzu Death animation play here
			}
			else if (GAME->GetCurrentLevel() == 2)
			{
				// Papa Bear here
			}
			else if (GAME->GetCurrentLevel() == 3)
			{
				// Lil' Prick here
			}
			else if (GAME->GetCurrentLevel() == 4)
			{
				// Lock Jaws Death animation
				if (IsMovingLeft() && GetCurrentAnimationName() != "LockJawDeadLeft")
					SetAnimation("LockJawDeadLeft");
				else if (GetCurrentAnimationName() != "LockJawDeadRight")
					SetAnimation("LockJawDeadRight");
			}

		}
		// Send enemies flying offscreen
		else if (GetID() == MELEE || GetID() == RANGED && opponent->GetID() == PLAYER)
		{
			// Deactivate enemy attacks
			SetAttackRect(SGD::Rectangle(-1, -1, -1, -1));

			// Get player to get facing directions
			const Player* player = dynamic_cast<const Player*>(opponent);

			// Make enemies fly away from player
			if (player->IsFacingLeft())
				SetVelocity(SGD::Vector{ -500.0f + player->GetVelocity().x, -200.0f });
			else
				SetVelocity(SGD::Vector{ 500.0f + player->GetVelocity().x, -200.0f });

			// Set Acceleration to 0
			SetAcceleration(SGD::Vector{ 0.0f, 0.0f });
			// Set Max speed to allow enemy to move faster
			SetMaxSpeed(10000.0f);

			// Set to being killed
			SetIsKilled(true);
		}
	}

	if (GetID() != PLAYER)
		SetInvulnerableTimer(0.5f);
	else
		SetInvulnerableTimer(1.5f);

	Game* g = GAME;

	if (GetID() == PLAYER)
	{
		g->PlayPlayerHurtAudio();
		g->PlayEnemyAttackAudio();
	}

	else if (GetID() == MELEE || GetID() == RANGED || GetID() == QUDZU || GetID() == PAPABEAR || GetID() == LOCKJAW)
	{
		g->PlayEnemyHurtAudio();
		g->PlayPlayerAttackAudio();
	}
}

void Entity::TileCollision(float dt)
{
	TileManager* tm = GAME->GetTileManager();

	// Local reference to current position
	SGD::Point startPosition = GetPosition();

	// Determine entity position after update
	SGD::Vector velocity = GetVelocity();
	SGD::Vector a = GetAcceleration();
	SGD::Point endPosition = GetPosition();

	velocity = velocity + a * dt;
	endPosition = endPosition + velocity * dt;

	// Store entity position locally (X coord of tile that will be checked)
	int keyX = (int)endPosition.x;
	// Store entity position locally (Y coord of tile that will be checked)
	int keyY = (int)endPosition.y;
	// Store how far into tile entity is
	int keyIndex = keyX % (int)tm->GetTileSize().width;

	// Convert keys into tile increments
	keyX /= (int)tm->GetTileSize().width;
	keyY /= (int)tm->GetTileSize().height;

	//------------------Check Ceiling Collision----------------------//

	int left = (int)GetPosition().x;
	int top = (int)(GetPosition().y - GetTopCollisionOffset());
	int tileAbove = tm->GetTileIndex(left / (int)tm->GetTileSize().width, (top - 1) / (int)tm->GetTileSize().height);

	// Check tile above player
	if (tileAbove != -5 && tileAbove != 1 && tileAbove != 2 && tileAbove != 3 && velocity.y < 0.0f)
	{
		SetVelocity(SGD::Vector(GetVelocity().x, 0.0f));
		SetAcceleration(SGD::Vector(GetAcceleration().x, 0.0f));
	}


	//--------------------Check Wall Collision-----------------------//
	int wallCollKeyX;
	int wallCollKeyY;
	if (IsOnGround())
		wallCollKeyY = (int)startPosition.y - 32; // Subtract 32 to prevent collision on tiles under you
	else
		wallCollKeyY = (int)startPosition.y - 6;

	wallCollKeyY /= (int)tm->GetTileSize().height;

	// Adjust position based on collision rect
	if (velocity.x > 0.0f)
		wallCollKeyX = (int)(endPosition.x + GetRightCollisionOffset());
	else
		wallCollKeyX = (int)(endPosition.x - GetLeftCollisionOffset());

	wallCollKeyX /= (int)tm->GetTileSize().width;

	int wallTile = tm->GetTileIndex(wallCollKeyX, wallCollKeyY);

	if (velocity.x > 0.0f && (wallTile == 18 || wallTile == 19))
	{
		SetPosition(SGD::Point((wallCollKeyX * tm->GetTileSize().width - GetRightCollisionOffset()), GetPosition().y));
		SetAcceleration(SGD::Vector(0.0f, GetAcceleration().y));
		SetVelocity(SGD::Vector(0.0f, GetVelocity().y));

		if (!IsOnGround())
		{
			IsOnWall(true);
			IsOnLeftWall(false);
			if (GetWallTime() <= 0.0f)
				StartWallTime();
		}
	}
	else if (velocity.x < 0.0f && (wallTile == 22 || wallTile == 23))
	{
		SetPosition(SGD::Point(((wallCollKeyX * tm->GetTileSize().width) + GetLeftCollisionOffset() + tm->GetTileSize().width), GetPosition().y));
		SetAcceleration(SGD::Vector(0.0f, GetAcceleration().y));
		SetVelocity(SGD::Vector(0.0f, GetVelocity().y));
		if (!IsOnGround())
		{
			IsOnWall(true);
			IsOnLeftWall(true);
			if (GetWallTime() <= 0.0f)
				StartWallTime();
		}
	}


	//-------------------Check Ground Collision----------------------//
	int tileWidth = (int)tm->GetTileSize().width;
	int tileHeight = (int)tm->GetTileSize().height;

	int startX;
	int startY = (int)startPosition.y;

	if (velocity.x > 0)
		startX = (int)startPosition.x - 1;
	else if (velocity.x < 0)
		startX = (int)startPosition.x + 1;
	else
		startX = (int)startPosition.x;

	int startIndex = startX % tileWidth;
	int endX = (int)endPosition.x;
	int endY = (int)endPosition.y;
	int endIndex = endX % tileWidth;

	int startTileX = startX / tileWidth;
	int startTileY = startY / tileHeight;
	int endTileX = endX / tileWidth;
	int endTileY = endY / tileHeight;

	Line player = Line(startPosition, endPosition);
	Line ground;


	if (velocity.y >= 0.0f)
	{
		if (startTileX <= endTileX)
		{
			for (int y = startTileY; y <= endTileY; ++y)
			{
				for (int x = startTileX; x <= endTileX; ++x)
				{
					for (int i = 0; i < tileWidth; ++i)
					{
						int height = tm->GetTileHeightMap(x, y, i);

						if (ground.GetStartPoint() == SGD::Point(-1.0f, -1.0f) && height != -1)
							ground.SetStartPoint(SGD::Point((float)(i + x * tileWidth), (float)(height + y * tileHeight)));
						else if (height != -1)
							ground.SetEndPoint(SGD::Point((float)(i + x * tileWidth), (float)(height + y * tileHeight)));
					}

					if (ground.GetStartPoint() != SGD::Point(-1.0f, -1.0f) && ground.GetEndPoint() != SGD::Point(-1.0f, -1.0f))
					{
						////////////////////////////////////////////////////
						// Known Bug Fix ID: 01
						// Implemented by: Cliff Baird
						// Extending the ground line by three pixels in each
						//  direction to cover space between tiles
						////////////////////////////////////////////////////

						if (ground.GetEndPoint().x == ground.GetStartPoint().x)
						{
							SGD::Point linePoint = ground.GetStartPoint();
							linePoint.x -= 3;
							ground.SetStartPoint(linePoint);
							linePoint = ground.GetEndPoint();
							linePoint.x += 3;
							ground.SetEndPoint(linePoint);
						}
						else
						{
							float slope = (ground.GetEndPoint().y - ground.GetStartPoint().y) / (ground.GetEndPoint().x - ground.GetStartPoint().x);
							SGD::Point linePoint = ground.GetStartPoint();
							linePoint.x -= 3;
							linePoint.y -= 3 * slope;
							ground.SetStartPoint(linePoint);
							linePoint = ground.GetEndPoint();
							linePoint.x += 3;
							linePoint.y += 3 * slope;
							ground.SetEndPoint(linePoint);
						}

						/////////////////////////////////////////////////////
						// End Known Bug Fix ID: 01
						/////////////////////////////////////////////////////

						SGD::Point intersection;
						if (ground.IntersectionPoint(player, intersection))
						{
							SetPosition(SGD::Point(GetPosition().x, intersection.y));
							IsOnGround(true);
							endY = (int)GetPosition().y;
							endTileY = endY / tileHeight;
							y = endTileY;
						}
					}
				}
			}
		}
		else
		{
			for (int y = startTileY; y <= endTileY; ++y)
			{
				if (IsOnGround())
					break;

				for (int x = startTileX; x >= endTileX; --x)
				{
					if (IsOnGround())
						break;

					for (int i = 0; i < tileWidth; ++i)
					{
						int height = tm->GetTileHeightMap(x, y, i);

						if (ground.GetStartPoint() == SGD::Point(-1.0f, -1.0f) && height != -1)
						{
							ground.SetStartPoint(SGD::Point((float)(i + x * tileWidth), (float)(height + y * tileHeight)));
							ground.SetEndPoint(SGD::Point((float)(i + x * tileWidth), (float)(height + y * tileHeight)));
						}
						else if (height != -1)
							ground.SetEndPoint(SGD::Point((float)(i + x * tileWidth), (float)(height + y * tileHeight)));
					}

					if (ground.GetStartPoint() != SGD::Point(-1.0f, -1.0f) && ground.GetEndPoint() != SGD::Point(-1.0f, -1.0f) && (endPosition.y > ground.GetStartPoint().y || endPosition.y > ground.GetEndPoint().y))
					{
						SGD::Point intersection;
						if (ground.IntersectionPoint(player, intersection))
						{
							SetPosition(SGD::Point(GetPosition().x, intersection.y));
							IsOnGround(true);
							endY = (int)GetPosition().y;
							endTileY = endY / tileHeight;
						}
					}
				}
			}
		}
	}

	if (IsOnGround())
	{
		/////////////////////////////////////////////////
		// Known Bug Fix ID: 08
		// Implemented by: Cliff Baird
		// Added an additional check to two tiles above current
		//  and reorganized tile checks to start at highest position
		/////////////////////////////////////////////////
		if (!CheckTile(endTileX, endTileY - 2, endIndex)) // check two tiles above
		{
			if (!CheckTile(endTileX, endTileY - 1, endIndex)) // check one tile above
			{
				if (!CheckTile(endTileX, endTileY, endIndex)) // check current tile
				{
					if (!CheckTile(endTileX, endTileY + 1, endIndex)) // check one tile below
					{
						IsOnGround(false);
					}
				}
			}
		}
		/////////////////////////////////////////////////////
		// End Known Bug Fix ID: 08
		/////////////////////////////////////////////////////
	}
	/////////////////////////////////////////////////////////
	// Known Bug Fix ID: 10
	// Implemented by: Cliff Baird
	// If player is not on ground and has started falling,
	//  check each tile for ground from start to finish of
	//  fall. Stop once ground has been found and set player
	//  on the ground.
	/////////////////////////////////////////////////////////
	else if (GetVelocity().y >= 0.0f)
	{
		for (int i = 0; i < endTileY - startTileY + 1; ++i)
		{
			if (CheckTile(endTileX, startTileY + i, endIndex))
			{
				IsOnGround(true);
				break;
			}
		}
	}
	/////////////////////////////////////////////////////////
	// End Known Bug Fix ID: 10
	/////////////////////////////////////////////////////////
}

bool Entity::CheckTile(int x, int y, int index)
{
	TileManager* tm = GAME->GetTileManager();

	int height = tm->GetTileHeightMap(x, y, index);
	int ground = (y * (int)tm->GetTileSize().height) + height;


	if (height != -1)
	{
		SetPosition(SGD::Point(GetPosition().x, (float)ground));
		return true;
	}
	return false;
}

bool Entity::CheckTile(int x, int y, int index, int endY)
{
	TileManager* tm = GAME->GetTileManager();

	int height = tm->GetTileHeightMap(x, y, index);
	int ground = (y * (int)tm->GetTileSize().height) + height;

	if (height != -1 && ground - endY < 32 && ground - endY > -32)
	{
		SetPosition(SGD::Point(GetPosition().x, (float)ground));
		return true;
	}
	return false;
}

// Accessors
int Entity::GetCurrentHP()
{
	return iCurHP;
}

int Entity::GetMaxHP()
{
	return iMaxHP;
}

bool Entity::IsAttacking()
{
	return bIsAttacking;
}

float Entity::GetAttackTimer()
{
	return fAttackTimer;
}

float Entity::GetAttackResetTime()
{
	return fAttackResetTime;
}

bool Entity::IsOnGround()
{
	return bOnGround;
}

bool Entity::IsJumping()
{
	return bJumping;
}

bool Entity::IsMovingLeft()
{
	return bMovingLeft;
}

void Entity::IsMovingLeft(bool direction)
{
	bMovingLeft = direction;
}

bool Entity::IsInvulnerable()
{
	return fInvulnerableTimer > 0.0f;
}

float Entity::GetInvulnerableTimer()
{
	return fInvulnerableTimer;
}

bool Entity::IsOnWall()
{
	return bOnWall;
}

float Entity::GetWallTime()
{
	return fWallTime;
}

bool Entity::IsFacingLeft()const
{
	return bFacingLeftRight;
}

bool Entity::PrevDirection()
{
	return bPreviousFacingDirection;
}

bool Entity::IsOnLeftWall()
{
	return bWallLeftRight;
}

// Mutators

void Entity::SetCurrentHP(int iHp)
{
	iCurHP = iHp;

	if (GetCurrentHP() > GetMaxHP())
		SetCurrentHP(GetMaxHP());
}
void Entity::SetMaxHP(int iHp)
{
	iMaxHP = iHp;
}

void Entity::IsAttacking(bool attack)
{
	bIsAttacking = attack;
}
void Entity::SetAttackTimer(float time)
{
	fAttackTimer = time;
}
void Entity::SetAttackResetTime(float time)
{
	fAttackResetTime = time;
}
void Entity::SetInvulnerableTimer(float time)
{
	fInvulnerableTimer = time;
}

void Entity::IsOnLeftWall(bool wall)
{
	bWallLeftRight = wall;
}
void Entity::IsOnGround(bool grounded)
{
	bOnGround = grounded;
	if (grounded)
	{
		SetAcceleration(SGD::Vector(GetAcceleration().x, 0.0f));
		SetVelocity(SGD::Vector(GetVelocity().x, 0.0f));
	}
}
void Entity::IsJumping(bool jump)
{
	bJumping = jump;
}
void Entity::StartWallTime()
{
	////////////////////////////////////////////////////////
	// Known Bug Fix ID: 07
	// Implemented by: Cliff Baird
	// Reduce the amount of time player is on wall from 0.5f
	//  to 0.25f
	////////////////////////////////////////////////////////
	fWallTime = 0.25f;
	////////////////////////////////////////////////////////
	// End Known Bug Fix ID: 07
	////////////////////////////////////////////////////////
}
void Entity::SetWallTime(float time)
{
	fWallTime = time;
}
void Entity::IsOnWall(bool wall)
{
	bOnWall = wall;
	if (!wall)
		SetWallTime(0.0f);
}

float Entity::GetParticleTimer()
{
	return fPartTimer;
}
void Entity::SetParticleTimer(float timer)
{
	fPartTimer = timer;
}

void Entity::IsFacingLeft(bool left)
{
	bFacingLeftRight = left;
}

void Entity::PrevDirection(bool prevDir)
{
	bPreviousFacingDirection = prevDir;
}